% =============================================================================
% Automated modeling in process-based qualitative reasoning
%  ** Output module. Contains predicates for outputting a model to GARP3
% 
% April - July 2007
% 
% Hylke Buisman   - hbuisman@science.uva.nl
% =============================================================================

% Dynamic declaration of entity name to object reference mapping
:- dynamic entity_instance/2.

%==============================================================================
% VISUALIZATION
%==============================================================================

% Export the generated model to GARP3
% output_model/1
% output_model(+Model)
output_model(Model) :-
	retractall(entity_instance(_,_)),
	
	% Create new model
	new_model(OldModel, NewModel),
	
	% Copy necessary data from old to new model
	copy_model(OldModel, NewModel),

	% Create conditional model fragments
	findall(mf(Cond, Cons), member(mf(Cond, Cons), Model), CondMFs),
	add_conditional_MFs(NewModel, CondMFs),
	removeall(Model, CondMFs, Model2),
	
	% Create main model fragment
	design:getModelFragmentDefinitionName(NewModel, 'Agent', ProcessMF),
	chain_list(ParChain, [ProcessMF]),
	design:changeModelFragmentDefinitionName(NewModel, 'Output', MFName),
	design:addModelFragmentDefinition(NewModel, @nil,  MFName, '', ParChain, true),
	design:getModelFragmentDefinitionName(NewModel, MFName, MF),
	
	% Add entities
	get_property(1, 3, system_elements(Scenario)),
	findall(A, 
		(
		member(instance(A, B), Scenario),
		pretty_atom_to_string(B, CB),
		(design:getEntityType(NewModel, CB, 'Entity');design:getEntityType(NewModel, CB, 'Agent'))
		), Entities),
	output_entities(NewModel,MF, 'condition', Entities),
	
	% Add quantities
	get_state_quantities(1, Quantities),
	output_quantities(NewModel,MF, 'consequence', Quantities),
	
	
	% Add configurations/assumptions
	get_property(1, 3, system_elements(Scenario)),
	findall(has_attribute(A, B, C), member(has_attribute(A, B, C), Scenario), Configs),
	output_configurations(NewModel,MF, Configs),
	
	% Add dependencies
	output_dependencies(NewModel,MF, Model2), !.

	
% Add conditional model fragments to the created model
% add_conditional_MFs/2
% add_conditional_MFs(+Model, +ConditionalMFs)
add_conditional_MFs(_, []).
add_conditional_MFs(Model, [mf(Conds, Cons)|Rest]) :-
	design:getModelFragmentDefinitionName(Model, 'Agent', AgentMF),
	chain_list(ParChain, [AgentMF]),
	design:changeModelFragmentDefinitionName(Model, 'Condition va', MFName), % Avoids duplicate names
	design:addModelFragmentDefinition(Model, @nil,  MFName, '', ParChain, true),
	design:getModelFragmentDefinitionName(Model, MFName, MF),
	
	append(Conds, Cons, AllDeps),
	output_necessary_components(Model, MF, AllDeps),
	
	add_conditional_MFs(Model, Rest).
	
	
% Wrapper for output_necessary_components/4
% output_necessary_components/3
% output_necessary_components(+Model, +MF, +Dependencies)
output_necessary_components(Model, MF, Deps) :-
	output_necessary_components(Model, MF, [], Deps).

% Output all components required when outputting given Dependencies
% This predicate is heavily biased in that is supposes that all
% passed dependencies are (in)equalities or value assignments.
% output_necessary_components/4
% output_necessary_components(+Model, +MF, +AccAddedQuantities, +Dependencies)
output_necessary_components(_, _, _, []).
output_necessary_components(Model, MF, Added, [model_dependency(Relation, [Q, Val])|T]) :-
	get_quantity_owner(Q, Entity),
	
	(member(Entity, Added)
	->
	 true;
	 (output_entities(Model, MF, 'condition', [Entity]),
	 get_all_quantities(Quantities),
	 member(quantity(Type, Q, Owner, QSpace), Quantities),
	 output_quantities(Model, MF, 'condition', [quantity(Type, Q, Owner, QSpace)]))
	 ),	
	 
	% Add inequalities
	entity_instance(Q/(MF), Q1Obj),

	determine_val_ineq(Q, Val, Relation, RealVal, RealRel),
	
	translate_inequality(RealRel, ND),
	
	pretty_atom_to_string(RealVal, PrettyVal),
	get(Q1Obj, quantitySpace, QuantitySpace),
	get(QuantitySpace, values, ValuesChain), 
	get(ValuesChain, find, @arg1?valueName == PrettyVal, ValueReferenceCopy),

	design:addInequalityInstance(Model, MF, ND, '', 'condition', 
	    Q1Obj, new(chain), currentValue, @nil,
	    Q1Obj, new(chain), quantityQSPoint, ValueReferenceCopy, @nil),
	
		
	output_necessary_components(Model, MF, [Entity|Added], T).
output_necessary_components(Model, MF, Added, AddedOut, [value(Q, _, V, _)|T]) :-
	get_quantity_owner(Q, Entity),
	(member(Entity, Added)
	->
	 true;
	 (output_entities(Model, MF, 'condition', [Entity]),
	 get_all_quantities(Quantities),
	 member(quantity(Type, Q, Owner, QSpace), Quantities),
	 output_quantities(Model, MF, 'condition', [quantity(Type, Q, Owner, QSpace)]))
	 ),
	
	output_dependencies(Model, MF, [value(Q, _, V,_)]),
	
	output_necessary_components(Model, MF, [Entity|Added], AddedOut, T).
output_necessary_components(Model, MF, Added, AddedOut, [_|T]) :-
	output_necessary_components(Model, MF, Added, AddedOut, T).
	
	
% Determine the good relation for output in case of (in)equalities
% If 'is greater than an interval' is given, it changes this
% to 'is greater than the point below it'
% determine_val_ineq/5
% determine_val_ineq(+Quantity, +Value, +Relation, -RealValue, -RealRelation)
determine_val_ineq(Q, Val, Relation, RealVal, Relation) :-
	get_qs_partition(Q, P),
	member(point(Val), P),
	functor(Val, RealVal, _).
determine_val_ineq(Q, Val, greater_or_equal, [], []) :-
	get_qs_partition(Q, P),
	nth0(0, P, Val).
determine_val_ineq(Q, Val, greater_or_equal, RealVal, greater) :-
	get_qs_partition(Q, P),
	nth0(N, P, Val),
	NN is N-1,
	nth0(NN, P, point(V)),
	functor(V, RealVal, _).
determine_val_ineq(Q, Val, smaller_or_equal, [], []) :-
	get_qs_partition(Q, P),
	nth0(N, P, Val),
	length(P, LP),
	N > LP.
determine_val_ineq(Q, Val, smaller_or_equal, RealVal, smaller) :-
	get_qs_partition(Q, P),
	nth0(N, P, Val),
	NN is N+1,
	nth0(NN, P, point(V)),
	functor(V, RealVal, _).


% Add model entities to GARP3 builder
% output_entities/4
% output_entities(+Model, +ModelFragment, +EntityNames)
output_entities(_, _, _, []).
output_entities(Model, MF, ConditionOrConsequence, [H|T]) :-
	get_entity_type(H, Type),
	pretty_atom_to_string(H, CH),
	pretty_atom_to_string(Type, EntityName),
	design:getEntityDefinitionName(Model, EntityName, Entity),
	design:addEntityInstance(Model, MF, Entity, CH, '', ConditionOrConsequence, @nil),
	get(MF?elements, tail, NewElement),
	assert(entity_instance(H/(MF), NewElement)),
	output_entities(Model, MF, ConditionOrConsequence, T).

	
% Attach the quantities to the created entities GARP3
% Note that the entities need to be created and object
% refs mapped via entity_instance/2
% output_quantities/4
% output_quantities(+Model, +ModelFragment, +QuantityNames)
output_quantities(_, _, _, []).
output_quantities(Model, MF, ConditionOrConsequence, [quantity(Type, Name, Owner, QSpace)|T]) :-
	pretty_atom_to_string(Type, CType),
	design:getQuantityDefinition(Model, CType, QuantityDef),
	pretty_atom_to_string(QSpace, CQSpace),
	design:getQuantitySpaceDefinition(Model, CQSpace, QuantitySpaceDef),
	
	% get parent instance
	entity_instance(Owner/(MF), OwnerObj),
	
	design:addQuantityInstance(Model, MF, QuantityDef, QuantitySpaceDef, 
			OwnerObj, new(chain), new(chain), '', ConditionOrConsequence, @nil),
	get(MF?elements, tail, NewElement),
	assert(entity_instance(Name/(MF), NewElement)),
	output_quantities(Model, MF, ConditionOrConsequence, T).
	
	
% Link entities with configurations and set assumptions
% output_configurations/3
% output_configurations(+Model, +ModelFragment, +Configurations)
output_configurations(_, _, []).
output_configurations(Model, MF, [has_attribute(Left, has_assumption, Right)|T]) :-
	pretty_atom_to_string(Right, CRight),
	entity_instance(Left/(MF), AbstractEntityInstance),
	design:getEntityDefinitionName(@model, CRight, D),
	design:addAssumptionInstance(Model, MF, D, '', AbstractEntityInstance, new(chain), @nil),
	output_configurations(Model, MF, T),!.
output_configurations(Model, MF, [has_attribute(Left, Name, Right)|T]) :-
	pretty_atom_to_string(Name, CName),
	design:getConfigurationDefinition(Model, CName, ConfigurationDef),
	 
	% get entities that take part in config.
	entity_instance(Left/(MF), LeftObj),
	entity_instance(Right/(MF), RightObj),
	
	design:addConfigurationInstance(Model, MF, ConfigurationDef, LeftObj, new(chain), RightObj, new(chain), '', 'condition', @nil),
	output_configurations(Model, MF, T),!.
output_configurations(Model, MF, [_|T]) :- % Predicate is used as skip when add failse
	output_configurations(Model, MF, T).   % necessary for skipping exo info
	
	
% Add the model dependencies
% output_dependencies/3
% output_dependencies(+Model, +ModelFragment, +Dependencies)
output_dependencies(_, _, []).
output_dependencies(Model, MF, [model_dependency(D, [Q1, Q2])|T]) :- % Output causal relations
	split_dependency_atom(D, Type, Sign, _),

	member(Type, [inf, prop]),
	translate_sign(Sign, NSign),
	
	handle_calculus(MF, Q1, _),
	handle_calculus(MF, Q2, _),

	entity_instance(Q1/(MF), Q1Obj),
	entity_instance(Q2/(MF), Q2Obj),
	design:addCausalDependency(Model, MF, Type, NSign, Q2Obj, new(chain),
		Q1Obj, new(chain), '', @nil),
	output_dependencies(Model, MF, T).
output_dependencies(Model, MF, [model_dependency(D, [Q1, Q2])|T]) :- % Output q_correspondences
	split_dependency_atom(D, q_correspondence, Mirror, _),
	
	handle_calculus(MF, Q1, _),
	handle_calculus(MF, Q2, _),
	entity_instance(Q1/(MF), Q1Obj),
	entity_instance(Q2/(MF), Q2Obj),
	design:addCorrespondenceInstance(Model, MF, 
	    @off, @off, Mirror, @off,
	    Q1Obj, new(chain), @nil,
	    Q2Obj, new(chain), @nil,
	    '', @nil),
	output_dependencies(Model, MF, T).
output_dependencies(Model, MF, [model_dependency(D, [Q1, Q2])|T]) :- % Output inequalities
	member(D, [equal, smaller, greater, smaller_or_equal, greater_or_equal]),
	translate_inequality(D, ND),
	
	handle_calculus(MF, Q1, Argument1Type),
	handle_calculus(MF, Q2, Argument2Type),
	entity_instance(Q1/(MF), Q1Obj),
	entity_instance(Q2/(MF), Q2Obj),

	design:addInequalityInstance(Model, MF, ND, '', 'consequence', 
	    Q1Obj, new(chain), Argument1Type, @nil,
	    Q2Obj, new(chain), Argument2Type, @nil, @nil),

	output_dependencies(Model, MF, T).
output_dependencies(Model, MF, [value(Q, _, V,_)|T]) :- % Output values
	entity_instance(Q/(MF), QObj),
	pretty_atom_to_string(V, PrettyV),
	get(QObj, quantitySpace, TargetQuantitySpace),
	get(TargetQuantitySpace, values, ValuesChain),	
	get(ValuesChain, find, @arg1?valueName == PrettyV, ValueReferenceCopy),
	design:addValueInstance(Model, MF, ValueReferenceCopy, QObj, new(chain), @off, 'consequence', @nil),
	output_dependencies(Model, MF, T).
	
	
%==============================================================================
% COPYING A MODEL
%==============================================================================
	
% Create a new model
% new_model/1
% new_model(-Model)
new_model(PreviousModel, NewModel) :-
	get(@model, '_value', PreviousModel),
	send(@app, newModel),
	get(@model, '_value', NewModel).
	
% Copy the basic info from Model1 to Model2
% copy_model/2
% copy_model(+Model1, Model2)
copy_model(Model1, Model2) :-
	% Copy hierarchies
	copy_hierarchy(Model1, Model2, 'Entity'),
	copy_hierarchy(Model1, Model2, 'Agent'),
	copy_hierarchy(Model1, Model2, 'Assumption'),
	
	% Copy quantity spaces
	chain_list(Model1?quantitySpaces, QSList),
	add_qspaces(Model2, QSList),
	
	% Copy quantity definitions
	chain_list(Model1?quantityDefinitions, QList),
	add_quantities(Model2, QList),
	
	% Copy Configuration definitions
	chain_list(Model1?configurationDefinitions, ConfList),
	add_configurations(Model2, ConfList), !.
	
% Copy the hierarcy of Type. Type can be 'Entity', 'Agent' or 'Assumption'
% copy_hierarchy/3
% copy_hierarchy(+Model1, +Model2, +Type)
copy_hierarchy(Model1, Model2, Type) :-
	design:completeEntityList(Model1, Type, AllEntities),
	send(AllEntities, delete_head),
	chain_list(AllEntities, ListEntities),
	add_entities(Model2, ListEntities).
	
% Adds a list of entity definitions to Model
% add_entities/2
% add_entities(+Model, +EntityReferenceList)
add_entities(_, []).
add_entities(Model, [H|T]) :-
	get(H, name, EntityName),
	get(H, parent, Parent),
	get(Parent, name, ParentName),
	design:addEntityDefinition(Model, EntityName, ParentName, '', @nil),
	add_entities(Model, T).
	
% Adds a list of quantities (definition references) to Model 
% add_quantities/2
% add_quantities(+Model, +QuantityList)
add_quantities(_, []).
add_quantities(Model, [H|T]) :-
	get(H, name, Label),
	get(H, allowedQuantitySpaces, QSs),
	chain_list(QSs, QSList),
	get_quantitySpace_chain(Model, QSList, QSChain),
	design:addQuantityDefinition(Model, @nil, Label, QSChain, ''),
	add_quantities(Model, T).
	
% Convert a list quantityspace objects to a chain
% get_quantitySpace_chain/3
% get_quantitySpace_chain(+Model,)
get_quantitySpace_chain(_, [], Chain) :-
	list_chain([], Chain).
get_quantitySpace_chain(Model, [H|T], Chain) :-
	get_quantitySpace_chain(Model, T, Chain),
	get(H, name, Name),
	design:getQuantitySpaceDefinition(Model, Name, QuantitySpaceDef),
	send(Chain, append, QuantitySpaceDef).
	
% Add a list of quantity spaces to Model
% add_qspaces/2
% add_qspaces(+Model, +QSpaceList)
add_qspaces(_, []).
add_qspaces(Model, [H|T]) :-
	get(H, name, Label),
	Label \= 'Mzp',
	get(H, values, Values),
	design:cloneValues(Values, ClonedValues),
	design:addQuantitySpaceDefinition(Model, @nil, Label, ClonedValues, ''),
	add_qspaces(Model, T).
add_qspaces(Model, [_|T]) :-
	add_qspaces(Model, T).
	
% Add a list of configurations to Model
% add_configurations/2
% add_configurations(+Model, +ConfigurationList)
add_configurations(_, []).
add_configurations(Model, [H|T]) :-
	get(H, name, ConfigurationDefName),
	design:addConfigurationDefinition(Model, @nil, ConfigurationDefName, ''),
	add_configurations(Model, T).


%==============================================================================
% SIMULATION
%==============================================================================

% Run a simulation on the given scenario name.
% This function assumes that @model points to the correct model
% simulate_model/1
% simulate_model(+ScenarioName)
simulate_model(ScenarioName) :-
	get(@app, findExportedObject, mf, ScenarioName, ScenarioObj),
	send(@app, startFullSimulation, ScenarioObj).
	
	
%==============================================================================
% AUXILLIARY PREDICATES
%==============================================================================
	
% Given the name of an instance in a model fragment return the object
% Works since instance names are unique
% get_entity_instance/2
% get_entity_instance(+ModelFragment, +InternName, -GarpInstance) :-
get_entity_instance(MF, InternName, Entity) :-
	pretty_atom_to_string(InternName, CInternName),
	get(MF?elements, find_all, message(@prolog, ==, @arg1?class_name, 'garpInstance'),	AllGarpInstances),
	get(AllGarpInstances, find_all, message(@prolog, ==, @arg1?name, CInternName),	Result),
	chain_list(Result, [Entity|_]).
	
% Add plus/min calculus if necessary. Checks if there is a min(Q1, Q2)
% or plus(Q1, Q2) embeded in an (in)equality statement. Based on this 
% it returns a value that can be used as an ArgumentType in
% design:addInequalityInstance
% handle_calculus/3
% handle_calculus(+ModelFragment, +InEqualityArg, -ArgumentType)
handle_calculus(MF, min(Q1, Q2), calculus) :-
	entity_instance(Q1/(MF), Q1Obj),
	entity_instance(Q2/(MF), Q2Obj),
	design:addCalculusInstance(@model, MF, min, 'quantity', 
		Q1Obj, new(chain), @nil, Q2Obj, new(chain), @nil,
		'', @nil),
	get(MF?elements, tail, NewElement),
	assert(entity_instance(min(Q1, Q2)/(MF), NewElement)), !.
handle_calculus(MF, plus(Q1, Q2), calculus) :-
	entity_instance(Q1/(MF), Q1Obj),
	entity_instance(Q2/(MF), Q2Obj),
	design:addCalculusInstance(@model, MF, plus, 'quantity', 
		Q1Obj, new(chain), @nil, Q2Obj, new(chain), @nil,
		'', @nil),
	get(MF?elements, tail, NewElement),
	assert(entity_instance(plus(Q1, Q2)/(MF), NewElement)), !.
handle_calculus(_, _, currentValue).

% Translates (in)equalities to internal representation
% translate_inequality/2
% translate_inequality(?PrettyForm, ?InternalForm)
translate_inequality(smaller, l).
translate_inequality(smaller_or_equal, leq).
translate_inequality(equal, eq).
translate_inequality(greater_or_equal, geq).
translate_inequality(greater, g).

% Translates pos/plus to and from neg/min
% translate_sign/2
% translate_sign(?PrettyForm, ?InternalForm)
translate_sign(pos, plus).
translate_sign(neg, min).

